#include "tools.hpp"

struct Config {
    std::streamoff skip_off;
    std::string con_file;
    std::string src_dir;
    std::string save_dir;
    std::string dst_dir;
    std::string tgtxt_dir;
    std::string tmp_dir;
};

struct Info {
    std::string docno;
    std::string url;
    std::string title;
    std::string content;
    std::streampos pos;
    std::streamoff size;
    void reset() {
        docno.clear();
        url.clear();
        title.clear();
        content.clear();
        pos = 0;
        size = 0;
    }
};

class Handle {
private:
    std::shared_ptr<Config> m_cfg;
    int m_tgtxt_idx;
    std::ofstream m_ofp;
public:
    Handle()
        : m_cfg(nullptr), m_tgtxt_idx(0) {}
    bool show() {
        print("CONTINUE FILE: ", m_cfg->con_file);
        print("SKIP POS: ", m_cfg->skip_off);
        print("DIR SRC : ", m_cfg->src_dir);
        print("DIR SAVE: ", m_cfg->save_dir);
        print("DIR DST : ", m_cfg->dst_dir);
        print("DIR TGTXT: ", m_cfg->tgtxt_dir);
        print("DIR TEMP: ", m_cfg->tmp_dir);
        print("Continue (N/y) ?");
        //
        std::string cmd;
        std::cin >> cmd;
        if (cmd == "y" || cmd == "yes") return true;
        return false;
    }

    std::shared_ptr<Info> getLastInfo(const std::string &file) {
        std::ifstream ifp;
        ifp.open(file);
        if (!ifp.is_open()) return nullptr;
        ifp.seekg(-100*1024, std::ios::end);

        auto info = std::make_shared<Info>();
        info->reset();

        std::string line;
        while(!ifp.eof()) {
            std::getline(ifp, line);
            if (Tools::same_begin(line, "pos:")) {
                auto str = line.substr(4, line.length() - 4);
                info->pos = Tools::str2any<std::streamoff>(str);
            } else if (Tools::same_begin(line, "size:")) {
                auto str = line.substr(5, line.length() - 5);
                info->size = Tools::str2any<std::streamoff>(str);
            }
        }

        ifp.close();
        
        return info;
    }

    bool same_file_name(const std::string &file1, const std::string &file2) {
        return Tools::path_file_name(file1) == Tools::path_file_name(file2);
    }

    bool has_file_name(std::vector<std::string> &done_files, const std::string &file) {
        for (auto it=done_files.begin(); it!=done_files.end(); ++it) {
            if (this->same_file_name(*it, file)) return true;
        }
        return false;
    }

    void init(const std::string &config_file) {
        auto cfg = std::make_shared<Config>();
        m_cfg = cfg;
        
        cfg->skip_off = 0;

        std::ifstream ifp;
        ifp.open(config_file);
        if (!ifp.is_open()) return;

        std::string line;
        while(!ifp.eof()) {
            std::getline(ifp, line);
            auto maps = Tools::split(line, "=");
            if (!maps || maps->size() != 2) continue;
            auto key = Tools::trim(maps->at(0));
            auto value = Tools::trim(maps->at(1));
            if (key == "src_dir") cfg->src_dir = value;
            else if (key == "save_dir") cfg->save_dir = value;
            else if (key == "dst_dir") cfg->dst_dir = value;
            else if (key == "tgtxt_dir") cfg->tgtxt_dir = value;
            else if (key == "tmp_dir") cfg->tmp_dir = value;
        }

        ifp.close();

        cfg->con_file = Tools::path_join(cfg->tgtxt_dir, "1") + ".tgtxt";

        if (!cfg->con_file.empty()) {
            auto info = this->getLastInfo(cfg->con_file);
            if (info) {
                cfg->skip_off = info->pos + info->size;
            }
        }
    }

    void start() {
        if (!this->show()) return;
        //
        std::vector<std::string> done_files;
        {
            int i_file_name = 0;
            auto tgtxts = Tools::list_file(m_cfg->save_dir, ".tgtxt");
            for (auto f=tgtxts->begin(); f!=tgtxts->end(); ++f) {
                done_files.push_back(*f);
                i_file_name = Tools::str2any<int>(Tools::path_file_name(*f));
                if (i_file_name >= m_tgtxt_idx) {
                    m_tgtxt_idx = i_file_name;
                }
            }
        }
        //
        {
            if (m_cfg->skip_off != 0) {
                this->open_file(m_cfg->con_file);
            }
        }
        //
        {
            auto files = Tools::list_file(m_cfg->src_dir, ".7z");
            int i = 1;
            for (auto f=files->begin(); f!=files->end(); ++f, ++i) {
                if (this->has_file_name(done_files, *f)) {
                    print("has file:", *f, " skip.");
                    continue;
                }
                print(getTimeStr());
                log("start:", i, "/", files->size(), " file:", *f);
                if (!this->is_open()) {
                    if (!this->open_file()) {
                        print("No Handle file:", *f);
                        log("error stop:", i, "/", files->size(), " file:", *f);
                        continue;
                    }
                }
                this->start(*f, files->size(), i);
                this->close_file();
                m_cfg->con_file.clear();
                m_cfg->skip_off = 0;
                log("stop:", i, "/", files->size(), " file:", *f);
                print(getTimeStr());
            }
        }
    }

private:
    bool open_file(const std::string &filename="") {
        if (!filename.empty()) {
            m_ofp.open(filename, std::ios::app);
            return m_ofp.is_open();
        } else {
            ++m_tgtxt_idx;
            auto file = Tools::path_join(m_cfg->tgtxt_dir, Tools::any2str<int>(m_tgtxt_idx)+".tgtxt");
            m_ofp.open(file, std::ios::app);
            return m_ofp.is_open();
        }
    }
    void close_file() {
        m_ofp.close();
    }
    bool is_open() {
        return m_ofp.is_open();
    }

    void start(const std::string &filename, int size, int i) {
        print("正在处理第 ", i, "/", size, " 个： ", filename);

        auto file_full_name = Tools::path_file_full_name(filename);
        auto file_name = Tools::path_file_name(filename);
        auto file_ext = Tools::path_file_ext(filename);
        auto dst_path = Tools::path_join(m_cfg->dst_dir, file_name);
        auto file = Tools::path_join(m_cfg->dst_dir, file_full_name);

        print("1.复制中...");
        // copy 7z to dst
        if (!Tools::has_file(file)) {
            if (!Tools::copy(filename, file)) {
                print("错误： 复制1失败！第 ", i, "/", size, " 个： ", filename);
                log("错误： 复制1失败！第 ", i, "/", size, " 个： ", filename);
                Tools::rm(file);
                return;
            }
        }

        print("  复制成功.");
        print("2.解压中...");
        // un7z
        if (!Tools::has_dir(dst_path)) {
            if (!Tools::u7z(file, dst_path)) {
                print("错误： 解压失败！第 ", i, "/", size, " 个： ", file);
                log("错误： 解压失败！第 ", i, "/", size, " 个： ", file);
                Tools::rm(dst_path);
                return;
            }
        }

        print("  解压成功.");
        print("3.处理中...");
        {
            // list files
            auto files = Tools::list_file(dst_path, "");
            int j = 1;
            for (auto f=files->begin(); f!=files->end(); ++f, ++j) {
                if (!handle(*f, i, j)) {
                    print("错误： 处理失败！第 ",
                        i, "/", size, ",",
                        j, "/", files->size(),
                        " 个： ", filename);
                    log("错误： 处理失败！第 ",
                        i, "/", size, ",",
                        j, "/", files->size(),
                        " 个： ", filename);
                    return;
                }
            }
        }

        print("  处理成功.");
        print("4.复制到save_dir中...");
        {
            // copy tgtxt to save_dir
            auto files = Tools::list_file(m_cfg->tgtxt_dir, ".tgtxt");
            int k = 1;
            for (auto f=files->begin(); f!=files->end(); ++f, ++k) {
                if (!Tools::copy(*f, Tools::path_join(m_cfg->save_dir, file_name)+".tgtxt")) {
                    print("错误： 复制2失败！第 ", k, "/", files->size(), " 个： ", *f);
                    log("错误： 复制2失败！第 ", k, "/", files->size(), " 个： ", *f);
                    return;
                }
            }
        }
        
        print("完成第 ", i, "/", size, " 个： ", filename);

        // clear
        Tools::rm(file);
        Tools::rm(dst_path);
        Tools::empty_dir(m_cfg->tgtxt_dir);

    }

    bool handle(const std::string &file, int i, int j) {
        std::ifstream fin(file);
        if (!fin.is_open()) {
            print("错误： 打开问价失败！ ", file);
            log("错误： 打开问价失败！ ", file);
            return false;
        }

        auto file_begin = fin.tellg();
        fin.seekg(0, std::ios::end);
        auto file_end = fin.tellg();

        auto file_size = file_end - file_begin;

        fin.seekg(m_cfg->skip_off, std::ios::beg);


        std::string line;
        Info info;
        std::string content;
        auto last_pos = fin.tellg();
        while (!fin.eof()) {
            std::getline(fin, line);
            if (Tools::same_begin(line, "<doc>")) {
                info.reset();
                content.clear();
                info.pos = fin.tellg() - line.length();
            } else if (Tools::same_begin(line, "</doc>")) {
                auto tmp = Tools::gethtml(content);
                bool needTrans = this->need_trans(tmp);
                if (!needTrans) {
                    info.content = tmp;
                } else {
                    Tools::gbk2utf8(tmp, info.content);
                    tmp = info.title;
                    Tools::gbk2utf8(tmp, info.title);
                }
                //
                info.size = fin.tellg() - info.pos;
                if ((info.pos-last_pos)/1024/1024 >= 100) {
                    print(info.pos+info.size, "/", file_size, ": ",
                        (info.pos+info.size)/1024/1024, "/", file_size/1024/1024);
                    last_pos = info.pos;
                }
                handle_info(info);
            } else if (Tools::same_begin(line, "<docno>")) {
                info.docno = Tools::get_html_text(line, "docno");
            } else if (Tools::same_begin(line, "<url>")) {
                info.url = Tools::get_html_text(line, "url");
            } else if (Tools::same_begin(line, "<title>")) {
                info.title = Tools::get_html_text(line, "title");
            } else {
                content.append(line);
            }
        }

        fin.close();
        return true;
    }

    bool handle_info(Info &info) {
        std::string tgtxt_dir = Tools::path_join(m_cfg->dst_dir, "tgtxt");
        auto content = Tools::html2text(info.content);

        m_ofp << std::endl << std::endl << "_tgtxt_page_" << std::endl << std::endl;
        m_ofp << "sublib:sougou" << std::endl;
        m_ofp << "title:" << info.title << std::endl;
        m_ofp << "url:" << info.url << std::endl;
        m_ofp << "pos:" << info.pos << std::endl;
        m_ofp << "size:" << info.size << std::endl;
        m_ofp << content << std::endl;

        return true;
    }

    bool need_trans(const std::string &content) {
        auto charset = Tools::to_lower(Tools::get_charset(content));
        if (charset.empty()) {
            return !Tools::is_str_utf8(content);
        }
        if (Tools::same_begin(charset, "utf")) return false;
        else if (Tools::same_begin(charset, "gb")) return true;
        return false;
    }
};

int main(int argc, char**argv) {
    
    std::string config_file = "config.cfg";
    if (argc == 2) {
        config_file = argv[1];
    } else if (argc != 1) {
        print("error args. ./bin [continue file:1]");
        print("    -> TGTXT DIR/1.tgtxt");
        return 0;
    }

    //
    std::shared_ptr<Handle> handle = std::make_shared<Handle>();
    handle->init(config_file);
    handle->start();

    return 0;
}